package com.inholland.weka;

import java.applet.Applet;
import java.awt.Button;
import java.awt.Choice;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Label;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import weka.core.Instances;

/**
 *
 * @author naita
 *
 *  this applet is a simulation of a sensor network (sensor matrix) which can be used to generate gestures.
 *  a gesture is defined as a movement of the hand (path) in the space resulting in an activation of some sensors.
 *  in this applet the movement of the hand is simulated as a mouse path, the sensors are simulated as
 *  rectangles with a certain space between them.
 *
 *  a gesture (mouse movement) will produce a sequence of observations. each observation has 5 variables t, x, y, i and j
 *  t in millisecond denotes the time
 *  x in pixels denotes the x coordinate (position) of the mouse at time t
 *  y in pixels denotes the y coordinate (position) of the mouse at time t
 *  i integer denotes the column index of the activated sensor (rectangle)
 *  j integer denotes the row index of the activated sensor (rectangle)
 *
 */
public class GestureApplet extends Applet implements MouseMotionListener, ActionListener {
	private static final long serialVersionUID = 738392231074111773L;
	private int x, y; // mouse coordinates
    // sensor matrix, used to paint rectangles (each regtangle defines a sensor).
    // the value of a cell (i,j)=1 when the sensor is activated. if not activated\
    // the value is 0.
    private int nCols = 5;
    private int nRows = 7;
    private int[][] sensorMatrix = new int[nCols][nRows];
    // GUI components
    Button btnReset = new Button("reset sensors");
    Button btnShowPath = new Button("show path");
    Button btnSave2File = new Button("add2train");
    Button btnGuess = new Button("Guess");
    Choice dropDown = new Choice();
    TextField tfDeltaT = new TextField();
    Label title = new Label("Gesture recognition applet");
    TextField tfFileName = new TextField("A");
    TextArea taOutput = new TextArea();
    // the sequence of observations generated by a gesture as a funtion of time (t)
    // is represented by 4 vectors: xVector, yVector, iVector and jVector.
    // the time between two successive values of a vector is deltaT milliseconds.
    Vector<Integer> xVector = new Vector<Integer>();
    Vector<Integer> yVector = new Vector<Integer>();
    Vector<Integer> iVector = new Vector<Integer>();
    Vector<Integer> jVector = new Vector<Integer>();
    int deltaT; // in milliseconds
	private String trainingSet = System.getProperty("user.dir")+File.separator+"data"+File.separator + "training_set.arff";
	private String testSet = null;

    /**
     * Initialization method that will be called after the applet is loaded
     * into the browser.
     *
     * initialize the sensorMatrix.
     * construct different GUI components.
     *
     */
    public void init() {

        this.setSize(700, 500);

        for (int i = 0; i < nCols; i++) {
            for (int j = 0; j < nRows; j++) {
                sensorMatrix[i][j] = 0;
            }
        }

        addMouseMotionListener(this);

        setLayout(null);
//(140, 330, 95, 20);
//(30, 330, 95, 20);

        title.setBounds(30, 30, 500, 40);
        Font font = new Font ("Dialog",Font.ITALIC,24);
        title.setFont(font);
        this.add(title);

        btnReset.setBounds(30, 265, 95, 20);
        this.add(btnReset);
        btnReset.addActionListener(this);

        btnShowPath.setBounds(140, 265, 95, 20);
        this.add(btnShowPath);
        btnShowPath.addActionListener(this);

        btnSave2File.setBounds(30, 290, 95, 20);
        this.add(btnSave2File);
        btnSave2File.addActionListener(this);

        btnGuess.setBounds(140, 290, 95, 20);
        this.add(btnGuess);
        btnGuess.addActionListener(this);
        tfFileName.setBounds(400, 50, 150, 20);

        dropDown.setBounds(100, 0, 100, 20);
        dropDown.add("J48");
        dropDown.add("Id3");
        dropDown.add("BF");
        dropDown.add("IB1");
        dropDown.add("IB5");
        dropDown.setBounds(30, 315, 100, 20);
        this.add(dropDown);
        tfFileName.setBounds(30, 240, 200, 20);

        this.add(tfFileName);

//        Label l = new Label("deltaT (ms)");
//        l.setBounds(300, 80, 80, 20);
//        this.add(l);
        tfDeltaT.setBounds(380, 80, 50, 20);
        tfDeltaT.setText("10");
//        this.add(tfDeltaT);
        deltaT = 10;

        taOutput.setBounds(300, 100, 300, 300);
        this.add(taOutput);
    }

    public void paint(Graphics g) {
        //update(g);
//                g.drawString("x=" + x + " y=" + y, 100, 20);
//        g.drawString("i=" + ((x - 100) / 20) + " j=" + ((y - 100) / 20), 100, 40);

        int xpos = 100, whith = 18;
        int ypos = 100, height = 18;

        // the first column of the sensors start from position xpos and
        // has as a whith value whith and hight the value height-2
        for (int j = 0; j < nRows; j++) {
            if (sensorMatrix[0][j] == 0) {
                g.setColor(Color.gray);
                g.fillRect(xpos, ypos + 20 * j, whith, height - 2);
                g.setColor(Color.black);
            } else {
                g.setColor(Color.red);
                g.fillRect(xpos, ypos + 20 * j, whith, height - 2);
                g.setColor(Color.black);
            }
        }

        // the other columns of sensors (index 1 ... nCols-2)  start from position xpos+2 and
        // have as a whith value whith-2 and hight the value height-2
        for (int i = 1; i < nCols - 1; i++) {
            for (int j = 0; j < nRows; j++) {
                if (sensorMatrix[i][j] == 0) {
                    g.setColor(Color.gray);
                    g.fillRect(xpos + 20 * i + 2, ypos + 20 * j, whith - 2, height - 2);
                    g.setColor(Color.black);
                } else {
                    g.setColor(Color.red);
                    g.fillRect(xpos + 20 * i + 2, ypos + 20 * j, whith - 2, height - 2);
                    g.setColor(Color.black);
                }
            }
        }

        // the last column of the sensors (index nCols-1)
        // has as a whith value whith and hight the value height-2
        for (int j = 0; j < nRows; j++) {
            if (sensorMatrix[nCols - 1][j] == 0) {
                g.setColor(Color.gray);
                g.fillRect(xpos + 20 * (nCols - 1) + 2, ypos + 20 * j, whith, height - 2);
                g.setColor(Color.black);
            } else {
                g.setColor(Color.red);
                g.fillRect(xpos + 20 * (nCols - 1) + 2, ypos + 20 * j, whith, height - 2);
                g.setColor(Color.black);
            }
        }
    }

    public void mouseMoved(MouseEvent me) {
        x = me.getX();
        y = me.getY();


        if (x >= 100 && x < 200 && y >= 100 && y < 240) {
            // if (x,y) is in the space between the rectangles. do nothing
            if ((x >= 118 && x < 122) || (x >= 138 && x < 142) || (x >= 158 && x < 162) || (x >= 178 && x < 182)
                    || (y >= 118 && y < 122) || (y >= 138 && y < 142) || (y >= 158 && y < 162) || (y >= 178 && y < 182)) {
                return;
            }

            int i = (x - 100) / 20;
            int j = (y - 100) / 20;
            System.out.println("i= " + i + ", j= " + j);
            sensorMatrix[i][j] = 1;

            // fill in different observation vectors
            xVector.add(x);
            yVector.add(y);
            iVector.add(i);
            jVector.add(j);

            repaint();

            try {
                Thread.sleep(deltaT);
            } catch (InterruptedException ex) {
                Logger.getLogger(GestureApplet.class.getName()).log(Level.SEVERE, null, ex);
            }
            System.out.println("x= " + x + ", y= " + y);
        }


    }

    /**
     * print de observation matrix in the text area
     */
    private void printObservationVector() {
        taOutput.append("t(ms)" + "\t" + "x" + "\t" + "y" + "\t" + "i" + "\t" + "j" + "\t" + "\n");
        for (int i = 0; i < xVector.size(); i++) {
            String sfrt = String.format("%5d\t %5d %5d %3d %3d", i * deltaT, xVector.get(i), yVector.get(i), iVector.get(i), jVector.get(i));
            taOutput.append(sfrt + "\n");
        }
        /*if (isValidPath() == false) {
            taOutput.append("NOT A VALID GESTURE PATH\n");
        }*/
    }

	public boolean alreadyContainsInformation (File file2beExamined) throws FileNotFoundException, IOException {
		FileInputStream fin =  new FileInputStream (file2beExamined);
		BufferedReader myInput = new BufferedReader (new InputStreamReader (fin));
		String line = myInput.readLine ();
		if (line != null ) {
			if ( line.contains ("@RELATION") ) {
				myInput.close();
				fin.close();
				return true;
			}

			else {
				myInput.close ();
				fin.close ();
				return false;
			}
		}

		return false;
	}

    /**
     * save time, xvector, yvector, iVector and jVector to a file fName
     * append to the file if the file exists
     *
     */

	private static final int CREATE_TEST_SET = 101;
	private static final int CREATE_TRAINING_SET = 102;

    private void save2file (String fName, int setType) {
        if (setType != GestureApplet.CREATE_TEST_SET && setType != GestureApplet.CREATE_TRAINING_SET) {
        	taOutput.append ("Dumb programmer! RUN!!!");
        	return;
        }

        /*if (isValidPath () == false) {
            taOutput.append ("Cannot save an  INVALID gesture path\n");
            return;
        }*/

        try {
        	File dataDir = new File ("data");					// use a data dir and create it if it's not yet there
        	if (!dataDir.exists () || !dataDir.isDirectory ()) {
        		dataDir.mkdir ();
        		System.out.println ("Created new data directory: " + dataDir.getAbsolutePath());
        	}

        	File file2beCreated;				// store the file in the data dir (and use the provided name)
        	if (System.getProperty ("os.name").equals ("Linux")) {
                    if(setType == CREATE_TEST_SET)
        		file2beCreated = new File (dataDir.getAbsolutePath() + "/" + fName + "_test_set.arff");
                    else
                        file2beCreated = new File (dataDir.getAbsolutePath() + "/" + "training_set.arff");
        	}
        	else {
                    if(setType == CREATE_TEST_SET)
        		file2beCreated = new File (dataDir.getAbsolutePath() + "\\" + fName + "_test_set.arff");
                    else
                        file2beCreated = new File (dataDir.getAbsolutePath() + "\\" + "training_set.arff");
        	}

            boolean writeHeader;
            file2beCreated.createNewFile ();
            FileWriter fstream;
            if(alreadyContainsInformation (file2beCreated) && setType == CREATE_TRAINING_SET){
                fstream = new FileWriter (file2beCreated, true);
                writeHeader = false;
            }
            else{
                fstream = new FileWriter (file2beCreated, false);
                writeHeader = true;
            }
            BufferedWriter out = new BufferedWriter (fstream);

            System.out.println ("Created new file: " + file2beCreated.getAbsolutePath());

            if (writeHeader) {
                out.write("@RELATION gestures\n");
                /*out.write("@ATTRIBUTE time NUMERIC\n");
                out.write("@ATTRIBUTE xPos NUMERIC\n");
                out.write("@ATTRIBUTE yPos NUMERIC\n");
                out.write("@ATTRIBUTE I NUMERIC\n");
                out.write("@ATTRIBUTE J NUMERIC\n");*/
                for(int x = 0;x < nCols;x++){
                    for(int y = 0;y < nRows;y++){
                        out.write("@ATTRIBUTE " + Integer.toString(x) + Integer.toString(y) + " {0,1}\n");
                    }
                }
                out.write("@ATTRIBUTE class {A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z}\n");
                out.write("@DATA\n");
            }

            /*for (int i = 0; i < xVector.size(); i++) {
                String sfrt = String.format("%d,%d,%d,%d,%d", i * deltaT, xVector.get(i), yVector.get(i), iVector.get(i), jVector.get(i)) + "," + tfFileName.getText().toUpperCase();
                out.write(sfrt + "\n");
            }*/
            String instance = new String();
            for(int x = 0;x < nCols;x++){
                for(int y = 0;y < nRows;y++){
                    instance = instance + Integer.toString(sensorMatrix[x][y]) + ",";
                }
            }
            out.write(instance + tfFileName.getText().toUpperCase() + "\n");

            taOutput.append ("Successfully exported gesture to "+ file2beCreated.getAbsolutePath());
            System.out.println ("Successfully exported gesture to "+ file2beCreated.getAbsolutePath());

            if (setType == GestureApplet.CREATE_TRAINING_SET)
            	this.trainingSet = file2beCreated.getAbsolutePath();

            else if (setType == GestureApplet.CREATE_TEST_SET)
            	this.testSet = file2beCreated.getAbsolutePath();

            out.close();				// Close the output stream
        } catch (Exception e) {			// Catch exception if any
            System.err.println("Error: " + e.getMessage());
        }
    }


    /**
     * This applet method takes the filenames of a test set and a training set,
     * loads them into WEKA and returns a J48 evaluation in a String.
     * @param trainingSet	- a string with the absolute filename of a training set file
     * @param testSet		- a string with the absolute filename of a test set file
     * @author shade
     * @return The WEKA evaluation as a String
     */
    public WekaResult evaluate (String trainingSet, String testSet) {
    	if (this.trainingSet == null || this.testSet == null)
    		return null;

    	WekaWorker weka = new WekaWorker ();
    	weka.loadFile (trainingSet);
    	weka.setupClassifierJ48 ();
    	return weka.getEvaluationJ48 (testSet);
    }

    /**
     * This method takes the filenames of a test set and a training set,
     * loads them into WEKA and returns a J48 classification of the test cases in an Instances object
     * @param trainingSet	- a string with the absolute filename of a training set file
     * @param testSet		- a string with the absolute filename of a test set file
     * @author shade
     * @return An Instances object, that contains classified test cases
     */
    public Instances classify (String trainingSet, String testSet) {
    	if (this.trainingSet == null || this.testSet == null)
    		return null;

    	WekaWorker weka = new WekaWorker ();
    	weka.loadFile (trainingSet);
        if(dropDown.getSelectedItem().equals("J48")){
            System.out.print(dropDown.getSelectedItem());
            weka.setupClassifierJ48 ();
            return weka.getClassification (testSet);
        }
        else if(dropDown.getSelectedItem().equals("Id3")){
            System.out.print(dropDown.getSelectedItem());
            weka.setupClassifierId3 ();
            return weka.getClassification (testSet);
        }
        else if(dropDown.getSelectedItem().equals("BF")){
            System.out.print(dropDown.getSelectedItem());
            weka.setupClassifierBF ();
            return weka.getClassification (testSet);
        }
        else if(dropDown.getSelectedItem().equals("IB1")){
            System.out.print(dropDown.getSelectedItem());
            weka.setupClassifierIB1 ();
            return weka.getClassification (testSet);
        }
        else{
            System.out.print(dropDown.getSelectedItem());
            weka.setupClassifierIB5 ();
            return weka.getClassification (testSet);
        }
    }

    /**
     * This method writes an instances object into a file (ARFF)
     * @param toBeWritten - Instances object
     * @param filename - The filename to be written to (produces an ARFF file)
     * @author shade
     */
    public void writeInstances (Instances toBeWritten, String filename) {
    	File writeFile = new File (filename);
    	if (toBeWritten == null) {
    		System.out.println ("There is no set to be written!!!");
    		return;
    	}

    	if (!writeFile.exists()) {
    		try {
				writeFile.createNewFile();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return;
			}
    	}

    	else if (writeFile.isDirectory()) {
    		System.out.println ("This is a directory!!!");
    		return;
    	}

    	WekaWorker weka = new WekaWorker ();
    	weka.loadInstances (toBeWritten);

    	if (weka.writeToDisk(filename))
    		System.out.println ("Instances were written!!!");
    	else
    		System.out.println ("Instances were NOT written!!!");

    	return;
    }

    /**
     * check if the gesture path (simulated by mouse movement) is valid or not
     * a path is invalid if one or more sensors is skipped (not activated) when
     * making a gesture.
     *
     */
    public boolean isValidPath() {

        for (int i = 0; i < iVector.size() - 1; i++) {
            int diff1 = (Integer) iVector.elementAt(i + 1) - (Integer) iVector.elementAt(i);
            int diff1_abs = Math.abs(diff1);
            int diff2 = (Integer) jVector.elementAt(i + 1) - (Integer) jVector.elementAt(i);
            int diff2_abs = Math.abs(diff2);
            if (diff1_abs >= 2 || diff2_abs >= 2) {
                return false;
            }
        }
        return true;

    }

    public void mouseDragged(MouseEvent e) {
    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == btnReset) {
            for (int i = 0; i < nCols; i++) {
                for (int j = 0; j < nRows; j++) {
                    sensorMatrix[i][j] = 0;
                }
            }
            repaint();

            deltaT = Integer.valueOf(tfDeltaT.getText());

            // reset observation vectors
            xVector.removeAllElements();
            yVector.removeAllElements();
            iVector.removeAllElements();
            jVector.removeAllElements();

        } else if (e.getSource() == tfDeltaT) {
            deltaT = Integer.valueOf(tfDeltaT.getText());
        } else if (e.getSource() == btnShowPath) {
            printObservationVector();
        } else if (e.getSource() == btnSave2File) {
            save2file(tfFileName.getText(), GestureApplet.CREATE_TRAINING_SET);
        }
        else if(e.getSource() == btnGuess)
        {
            save2file(tfFileName.getText(), GestureApplet.CREATE_TEST_SET);
            WekaResult result = this.evaluate(this.trainingSet, this.testSet);
            taOutput.setText("");
            taOutput.append(result.summaryString);
            Instances labled = classify(trainingSet, testSet);
            for(int i = 0;i < labled.numInstances();i++){
                taOutput.append("The letter is:\n" + labled.instance(i).stringValue(35));
            }
        }
    }
}